package org.os.framework.redis;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;

import java.lang.reflect.Method;
import java.util.Optional;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.os.framework.context.config.SpringContext;
import org.os.framework.context.model.HttpException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 分布式锁切面
 * 
 * @author weizuxiao
 */
@Slf4j
@Aspect
@ConditionalOnProperty(name = "spring.redisson.enable", havingValue = "true")
@AutoConfigureAfter(RedissonConfig.class)
@Configuration
public class RedissonLockAspect {

    @Pointcut("@annotation(org.os.framework.extensions.redis.RedissonLock)")
    public void pointCut() {
        log.info("加载分布式锁切面...");
    }

    /**
     * around切面
     * 
     * @param joinPoint
     * @return
     */
    @SneakyThrows
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        String key = parseKey(joinPoint);
        RLock rLock = SpringContext.getBean(RedissonClient.class).getLock(key);
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RedissonLock locker = method.getAnnotation(RedissonLock.class);
        if (locker.fastFailure()) {
            if (rLock.tryLock()) {
                try {
                    return joinPoint.proceed();
                } finally {
                    rLock.unlock();
                }
            } else {
                throw new HttpException(HttpStatus.LOCKED.name(), locker.message());
            }
        } else {
            rLock.lock();
            try {
                return joinPoint.proceed();
            } finally {
                rLock.unlock();
            }
        }
    }

    /**
     * 获取key, spel表达式
     * 
     * @param el
     * @param joinPoint
     * @return
     */
    @SuppressWarnings("null")
    @NonNull
    private String parseKey(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RedissonLock lock = method.getAnnotation(RedissonLock.class);
        if (!Optional.ofNullable(lock).isPresent()) {
            return "";
        }
        String value = lock.value();
        String key = lock.key();
        if (null != key && !"".equals(key)) {
            ParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
            String[] names = discover.getParameterNames(method);
            Object[] args = joinPoint.getArgs();
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(key);
            EvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < names.length; i++) {
                String k = names[i];
                if (null != k) {
                    context.setVariable(k, args[i]);
                }
            }
            String suffix = expression.getValue(context, String.class);
            return value + ":" + suffix;
        }
        return value;
    }
}
